<?php
/**
 * Created by PhpStorm.
 * User: lengbin
 * Date: 2019-02-22
 * Time: 16:59
 */

namespace common\core\mysql;

use yii\console\Exception;
use yii\db\Migration;
use yii\db\TableSchema;
use yii\helpers\VarDumper;

class Schema extends \yii\db\mysql\Schema
{

    /**
     * Returns all indexes for the given table.
     *
     * Each array element is of the following structure:
     *
     * ```php
     * [
     *     'IndexName1' => ['col1' [, ...]],
     *     'IndexName2' => ['col2' [, ...]],
     * ]
     * ```
     *
     * @param TableSchema $table the table metadata
     *
     * @return array all unique indexes for the given table.
     */
    public function findIndexes($table)
    {
        $sql = $this->getCreateTableSql($table);
        $indexes = [];

        $regexp = '/KEY\s+\`(.+)\`\s*\((\`.+\`)+\)/mi';
        if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $indexName = $match[1];
                $indexColumns = array_map('trim', explode('`,`', trim($match[2], '`')));
                $indexes[$indexName] = $indexColumns;
            }
        }

        return $indexes;
    }

    protected function renderHead($params)
    {
        $php = "<?php \n\n";
        if (!empty($params['namespace'])) {
            $namespace = $params['namespace'];
            $php .= <<<php
namespace {$namespace}; \n

php;
        }
        $php .= <<<php
use yii\db\Migration; \n

/**
 * Class {$params['className']}
 */
class {$params['className']} extends Migration
{

php;
        return $php;
    }

    protected function renderFoot($params)
    {
        return <<<php

    /*
    // Use up()/down() to run migration code without a transaction.
    public function up()
    {

    }

    public function down()
    {
        echo "{$params['className']} cannot be reverted.";

        return false;
    }
    */
}   
php;
    }

    protected function renderTableOptions()
    {
        return <<<php
\$tableOptions = null;
        if (\$this->db->driverName === 'mysql') {
            // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
            \$tableOptions = 'CHARACTER SET {$this->db->charset} COLLATE {$this->db->charset}_unicode_ci ENGINE=InnoDB';
        }
php;
    }

    protected function renderTableComment($table)
    {
        $sql = $this->getCreateTableSql($table);
        $regexp = '/ENGINE.*COMMENT=(.*)?/mi';
        preg_match($regexp, $sql, $comm);
        $comment = '';
        if (!empty($comm)) {
            $comment = ' . " COMMENT=' . $comm[1] . '"';
        }
        return $comment;
    }

    protected function getTableColumn($migrate, $column)
    {
        if ($column->type === self::TYPE_TINYINT) {
            $column->type = 'tinyInteger';
        }

        if ($column->type === self::TYPE_SMALLINT) {
            $column->type = 'smallInteger';
        }

        if ($column->type === self::TYPE_BIGINT) {
            $column->type = 'bigInteger';
        }

        if (!method_exists($migrate, $column->type)) {
            $php = $column->dbType;
            if (!$column->allowNull) {
                $php .= ' NOT NULL';
            }
            if ($column->defaultValue !== null) {
                $php .= ' DEFAULT "' . $column->defaultValue . '"';
            }
            if (!empty($column->comment)) {
                $php .= ' COMMENT "' . $column->comment . '"';
            }
        } else {
            switch ($column->type) {
                case 'decimal':
                case 'money':
                    $php = '$this->' . $column->type . "($column->size, $column->scale)";
                    break;
                case 'text':
                case 'date':
                case 'boolean':
                case 'json':
                    $php = '$this->' . $column->type . "()";
                    break;
                default:
                    $php = '$this->' . $column->type . "($column->size)";
                    break;
            }
            if (!$column->allowNull) {
                $php .= '->notNull()';
            }
            if ($column->defaultValue !== null) {
                if (is_int($column->defaultValue)) {
                    $php .= "->defaultValue(" . $column->defaultValue . ")";
                } else {
                    $php .= "->defaultValue('" . $column->defaultValue . "')";
                }
            }
            if (!empty($column->comment)) {
                $php .= "->comment('" . $column->comment . "')";
            }
        }
        return $php;
    }

    protected function renderTableIndex($table, $primaryKey)
    {
        $str = '';
        $data = [];
        if (!empty($primaryKey)) {
            $data[] = "\$this->addPrimaryKey('{$table->fullName}_pk', '{$table->fullName}', '{$primaryKey}');";
        }

        $uniqueIndexes = $this->findUniqueIndexes($table);
        if (!empty($uniqueIndexes)) {
            foreach ($uniqueIndexes as $key => $uniqueIndex) {
                $ui = "['" . implode("','", $uniqueIndex) . "']";
                $data[] = "\$this->createIndex('$key', '{$table->fullName}', $ui, true);";
            }
        }

        $indexes = $this->findIndexes($table);
        if (!empty($indexes)) {
            foreach ($indexes as $key2 => $index) {
                $i = "['" . implode("','", $index) . "']";
                $data[] = "\$this->createIndex('$key2', '{$table->fullName}', $i);";
            }
        }

        if (!empty($data)) {
            foreach ($data as $d) {
                $str .= <<<php
    
        {$d}
php;
            }
        }
        return $str;
    }

    protected function renderUp($table, $migrate)
    {
        $data = [];
        $primaryKey = '';
        foreach ($table->columns as $column) {
            $cm = '';
            if ($column->isPrimaryKey) {
                if ($column->autoIncrement) {
                    $cm = '$this->primaryKey()';
                } else {
                    $primaryKey = $column->name;
                }
            }
            if (empty($cm)) {
                $cm = $this->getTableColumn($migrate, $column);
            }
            $data[] = <<<php
            '{$column->name}' => {$cm},
php;
        }

        $comment = $this->renderTableComment($table);
        $c = implode("\n", $data);
        $t = <<<php
        
        \$this->createTable("{{%$table->fullName}}", [
{$c}
        ], \$tableOptions{$comment});
php;
        $add = $this->renderTableIndex($table, $primaryKey);
        if (!empty($add)) {
            $t .= $add;
        }
        return $t;
    }

    protected function renderDown($table)
    {
        return <<<php
        \$this->dropTable('{{%{$table->fullName}}}');
php;

    }

    protected function renderDownEmpty($table)
    {
        return <<<php
        \$this->truncateTable('{{%{$table->fullName}}}');
php;

    }

    protected function renderBody($data)
    {
        $up = implode("\n", $data['up']);
        if (is_array($data['down'])) {
            $down = implode("\n", $data['down']);
        } else {
            $down = $data['down'];
        }
        $php = <<<php

    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        {$up}
    }

    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
{$down}
    }
     
php;
        return $php;
    }

    /**
     * 通过sql生成migrate脚本
     *
     * @param array $params
     *
     * @return string
     */
    public function createSqlScript(array $params)
    {
        $template = $this->renderHead($params);
        $tables = $this->getTableSchemas();
        $data = [
            'up'   => [],
            'down' => [],
        ];
        if (!empty($tables)) {
            $data['up'][] = $this->renderTableOptions();
        }
        $migrate = new Migration();
        foreach ($tables as $table) {
            $data['up'][] = $this->renderUp($table, $migrate);
            $data['down'][] = $this->renderDown($table);
        }
        if (empty($data['down'])) {
            $data['down'] = <<<php
        
        echo "{$params['className']} cannot be reverted.";

        return false;
php;
        }

        $template .= $this->renderBody($data);
        $template .= $this->renderFoot($params);
        return $template;
    }

    protected function renderUpInit($table)
    {
        $field = $row = [];
        $results = $this->db->createCommand('select * from ' . $table->fullName)->queryAll();
        foreach ($results as $result) {
            if (empty($field)) {
                $field = array_keys($result);
            }
            $rr = array_values($result);
            $row[] = "['" . implode("','", $rr) . "']";
        }
        $f = "['" . implode("','", $field) . "']";
        $r = "[" . implode(", ", $row) . "]";
        return <<<php
        
        \$this->batchInsert("{{%$table->fullName}}", {$f}, 
        {$r});
php;
        ;
    }

    /**
     * 初始化sql数据
     *
     * @param array $params
     *
     * @return string
     */
    public function initSqlScript(array $params)
    {
        $template = $this->renderHead($params);
        $tables = $params['table'];
        $data = [
            'up'   => [],
            'down' => [],
        ];
        if (!empty($tables)) {
            $data['up'][] = $this->renderTableOptions();
        }
        foreach ($tables as $table) {
            $data['up'][] = $this->renderUpInit($table);
            $data['down'][] = $this->renderDownEmpty($table);
        }
        if (empty($data['down'])) {
            $data['down'] = <<<php
        
        echo "{$params['className']} cannot be reverted.";

        return false;
php;
        }

        $template .= $this->renderBody($data);
        $template .= $this->renderFoot($params);
        return $template;
    }

}